#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QFile>
#include <QTextStream>

#include "abstractpageparser.h"
#include "utils.h"

namespace Jcr
{
class AbstractPageParserPrivate
{
public:
    ~AbstractPageParserPrivate();
    bool writeDataToFile(const std::string& data);

    QNetworkAccessManager* netMgr = nullptr;
    QUrl url;
    QString localHtmlFile;
    QString writeFile;
    GumboOutput* output = nullptr;
};

AbstractPageParserPrivate::~AbstractPageParserPrivate()
{
    if (output)
        gumbo_destroy_output(&kGumboDefaultOptions, output);
    delete netMgr;
}

bool AbstractPageParserPrivate::writeDataToFile(const std::string &data)
{
    if (data.empty()) {
        qDebug() << "Dumping html content to file failed: empty content.";
        return false;
    }

    QFile outFile(writeFile);
    if (!outFile.open(QIODevice::WriteOnly)) {
        qDebug() << "Dumping html content to file failed: [" << writeFile << "] desc: " << outFile.errorString();
        return false;
    }

    QTextStream outS(&outFile);
    outS << data.c_str();

    outFile.close();
    return true;
}

AbstractPageParser::AbstractPageParser(QObject *parent) : QObject(parent)
{
    d_ptr = new AbstractPageParserPrivate;
}

AbstractPageParser::~AbstractPageParser()
{
    delete d_ptr;
}

void AbstractPageParser::setUrl(const QUrl& url)
{
    if (d_ptr->url != url)
        d_ptr->url = url;
}

QUrl AbstractPageParser::url() const
{
    return d_ptr->url;
}

void AbstractPageParser::setFile(const QString& file)
{
    if (d_ptr->localHtmlFile != file)
        d_ptr->localHtmlFile = file;
}

QString AbstractPageParser::file() const
{
    return d_ptr->localHtmlFile;
}

void AbstractPageParser::setWriteFile(const QString& writeFile)
{
    if (d_ptr->writeFile != writeFile)
        d_ptr->writeFile = writeFile;
}

QString AbstractPageParser::writeFile() const
{
    return d_ptr->writeFile;
}

void AbstractPageParser::run()
{
    if (d_ptr->url.isEmpty() && d_ptr->localHtmlFile.isEmpty()) {
        emit finished();
        return;
    }

    // parsing local file has higher priority
    if (!d_ptr->localHtmlFile.isEmpty()) {
        QFile htmlFile(d_ptr->localHtmlFile);
        if (!htmlFile.open(QIODevice::ReadOnly)) {
            qDebug() << "Failed to parse local html file: " << d_ptr->localHtmlFile;
            emit finished();
            return;
        }
        parsePage(htmlFile.readAll());
    } else {

        if (!d_ptr->netMgr) {
            d_ptr->netMgr = new QNetworkAccessManager();
            connect(d_ptr->netMgr, SIGNAL(finished(QNetworkReply*)), this, SLOT(onReply(QNetworkReply*)));
        }

        QNetworkRequest request;
        request.setUrl(d_ptr->url);
        networkMgr()->get(request);
    }
}

void AbstractPageParser::onReply(QNetworkReply *reply)
{
    parsePage(reply->readAll());
}

QNetworkAccessManager* AbstractPageParser::networkMgr() const
{
    return d_ptr->netMgr;
}

void AbstractPageParser::parsePage(const QByteArray &data)
{
    std::string strData = Jcr::Utils::gbk2Utf8(data.toStdString());

    if (!d_ptr->writeFile.isEmpty())
        d_ptr->writeDataToFile(strData);

    d_ptr->output = gumbo_parse(strData.data());
    if (d_ptr->output)
        traverseNode(d_ptr->output->root);
    finalize();
}

GumboOutput* AbstractPageParser::output() const
{
    return d_ptr->output;
}

void AbstractPageParser::traverseNode(GumboNode *root)
{
    if (root->type != GUMBO_NODE_ELEMENT)
        return;

    processNode(root);

    GumboVector* children = &root->v.element.children;
    for (unsigned int i = 0; i < children->length; ++i)
        traverseNode(static_cast<GumboNode*>(children->data[i]));
}


} // namespace jcr
